#pragma once
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <new>
#include <cstdio>
struct memrec_t {
  size_t size, cnt, peak;
};
struct meminfo_t {
  void *raw;
  size_t size;
  memrec_t *rec;
};
/* layer 1.0: Malloc/Free with type support */
/* should be inlined since they are only wrappers of functions */
namespace esmd {

/* layer 0.5 is visible */
extern void deallocate(void *ptr);
extern void *allocate(size_t size, memrec_t *rec);
extern void *reallocate_raw(void *ptr, size_t size);
template <typename T>
__always_inline T *allocate(int n, memrec_t *rec) {
  return (T *)allocate(sizeof(T) * n, rec);
}
template <typename T>
__always_inline T *allocate(int n, memrec_t *rec, const T& initial_value) {
  T* ret = (T*) allocate(sizeof(T) * n, rec);
  for (int i = 0; i < n; i ++) {
    ret[i] = initial_value;
  }
  return ret;
}
template <typename T>
__always_inline T *reallocate(T *ptr, size_t n) {
  return (T *)reallocate_raw((void *)ptr, n * sizeof(T));
}
template <typename T, typename... Ts>
__always_inline T *create(int n, memrec_t *rec, Ts... args) {
  T *ret = allocate<T>(n, rec);
  for (int i = 0; i < n; i++) {
    new (ret + i) T(args...);
  }
  return ret;
}

template <typename T>
__always_inline void destroy(T *ptr) {
  meminfo_t *info = (meminfo_t *)ptr - 1;
  for (int i = 0; i * sizeof(T) < info->size; i++) {
    ptr[i].~T();
  }
  dealloc(ptr);
}

/* I'm not sure about dealing this. I think this can be visible */
extern memrec_t *get_memrec(const char *);

// /* layer 1.5: allocator with type support */
// /* can be inlined since all methods are very short */
// /* major meaning of this is to store a memrec, especially before the hash tab is created */
// struct allocator {
//   __always_inline allocator(memrec_t *rec) : rec(rec) {}
//   /* this is actually a L3 function */
//   __always_inline allocator(const char *name) : rec(get_memrec(name)) {}
//   template <typename T>
//   __always_inline T *allocate(int n) {
//     return esmd::allocate<T>(n, rec);
//   }
//   template <typename T, typename... Ts>
//   __always_inline T *create(int n, Ts... args) {
//     return esmd::create<T>(n, rec, args...);
//   }
//   template <typename T>
//   __always_inline void destroy(T *ptr) {
//     esmd::destroy(ptr);
//   }
//   template <typename T>
//   __always_inline T *reallocate(T *ptr, int n) {
//     return esmd::reallocate(ptr, n);
//   }
//   template <typename T>
//   __always_inline void deallocate(T *ptr) {
//     esmd::deallocate(ptr);
//   }

//   private:
//   memrec_t *rec;
// };

/*layer 2: pool based on allocator*/
template <typename T>
struct pool_t {
  __always_inline pool_t(memrec_t *rec, int initial_size = 32) : cap(0), nempty(0), rec(rec) {
    nempty = 0;
    nhead = 0;
    grow(initial_size);
  }

  __always_inline void grow(int grow_size) {
    T *head = allocate<T>(grow_size, rec);
    if (free_list != NULL) {
      free_list = reallocate(free_list, cap + grow_size);
    } else {
      free_list = allocate<T *>(cap + grow_size, rec);
    }
    for (int i = 0; i < grow_size; i++) {
      free_list[nempty++] = head + i;
    }
    head_list[nhead++] = head;
    cap += grow_size;
  }
  __always_inline void grow() {
    int grow_size = (cap + 1) >> 1;
    grow(grow_size);
  }
  __always_inline T *get() {
    if (nempty == 0)
      grow();
    return free_list[--nempty];
  }
  __always_inline void put(T *ptr) {
    free_list[nempty++] = ptr;
  }

  private:
  // allocator allocator_stor;
  // allocator *alloc;
  memrec_t *rec;
  T **free_list;
  T *head_list[64];
  int cap, nempty;
  int nhead;
};
template <typename T>
__always_inline T *allocate(int n, const char *name) {
  return allocate<T>(n, get_memrec(name));
}
template <typename T>
__always_inline T *allocate(int n, const char *name, const T& initial_value) {
  return allocate(n, get_memrec(name), initial_value);
}
template <typename T, typename... Ts>
__always_inline T *create(int n, const char *name, Ts... args) {
  return create<T>(n, get_memrec(name), args...);
}
/* layer 3 : auto name to memrec_t mapping */
__always_inline void *allocate(size_t size, const char *name) {
  return allocate(size, esmd::get_memrec(name));
}
void print_memusage(FILE *f=stdout);
} // namespace esmd
